import re, os
import time, cStringIO
from paste.httpexceptions import *

from urlparse import urlparse
from datetime import datetime
from dateutil.tz import *
from brightcontent.util import UrlDispatch, get_base_url, fixup_namespaces, quick_xml_multiscan, web_trigger
from brightcontent.core.store import COMMON_PREFIXES
# from brightcontent.util import get_clean_title
import amara

from amplee.loader import loader
from amplee.handler.store.wsgi import Service, Store

try:
    #from shove.cache.memcached import MemCached
    from shove.cache.memory import MemoryCache
    cache_enabled = True
except ImportError:
    cache_enabled = False

entries_per_page = 10

ENTRY_ENVELOPE_XML = 'entry.xml'
POST_COLLECTION_TRIGGER = '/bctriggers/post-collection?id=%s'

class WsgiAtomPublishingProtocol(object):
    def __init__(self, application, store, app_conf):
        self.m = UrlDispatch(self.urls)
        #self.entry_m = UrlDispatch(self.entry_urls)

        self.application = application
        self.store = store
        # this is temporary until the config middleware is working correctly
        self.app_conf = app_conf

        amplee_store_conf = self.app_conf['amplee_config']
        amplee_base_path = self.app_conf['amplee_base_path']
        service, config = loader(amplee_store_conf, base_path=amplee_base_path)

        # This would normally be done by the loader() call above
        # but due to BC's design it's easier to do it this way share the same instance
        # amplee may simplify that in the future
        service.store.storage = self.store
        service.store.media_storage = self.store
    
        self.service = Service(service)
        self.stores = {}
        for workspace in service.workspaces:
            for collection in workspace.collections:
                service.store.storage.create_container(collection.name_or_id)

                # Again this should be done by the loader but because
                # both methods are attached to the store instance we must
                # do it that way. That may change in the future.
                handler = collection.handlers_mapping['application/atom+xml']
                handler.member_type.params['entry_id_creator'] = self.store.make_iri_id
                handler.member_type.params['name_creator'] = self.store.make_filename

                if cache_enabled:
                    collection.set_cache(MemoryCache('memory://')) #MemCached('memcache://localhost'))

                path_info = urlparse(collection.base_edit_uri)[2].split('/', 2)[-1].strip('/')
                self.stores[path_info] = Store(collection, strict=False)

    def service(self, environ, start_response, method, **kw):
        def GET():
            #TODO: implment a service document based on either the config or discovery from store(s)
            return HTTPNotImplemented()(environ, start_response)
        allowed_methods = {'GET': GET}
        if method in allowed_methods:
            #return allowed_methods[method]()
            return self.service.get_service(environ, start_response)
        else:
            return HTTPMethodNotAllowed()(environ, start_response)

    def entry(self, environ, start_response, method, **args):
        collection = args.get('collection', None)
        entry = args.get('entry', None)
        # we locally support http://wsgi.org/wsgi/Specifications/routing_args
        # as amplee expects it
        environ['wsgiorg.routing_args'] = ((), {'rid': entry})
        
        if collection not in self.stores:
            return HTTPNotFound()(environ, start_response)

        store = self.stores[collection]
        #Use closures rather than inner classes so we can peserve the "scope" of self
        def GET():
            return store.get_member(environ, start_response)
            
        def PUT():
            return store.update_member(environ, start_response)

        def DELETE():
            return store.delete_member(environ, start_response)
        
        allowed_methods = {'GET': GET, 'PUT' : PUT, 'DELETE': DELETE}
        if method in allowed_methods:
            return allowed_methods[method]()
        else:
            return HTTPMethodNotAllowed()(environ, start_response)

    def collection(self, environ, start_response, method, **args):
        collection_name = args.get('collection', None)
        if collection_name not in self.stores:
            return HTTPNotFound()(environ, start_response)

        store = self.stores[collection_name]
        #Use closures rather than inner classes so we can peserve the "scope" of self
        def GET():
            return store.get_collection(environ, start_response)
            #return self.get_collection(environ, start_response)

        def POST():
            # web_trigger(environ, POST_COLLECTION_TRIGGER%entry_filename)
            return store.create_member(environ, start_response)
            #return self.post_collection(environ, start_response)

        allowed_methods = {'GET': GET, 'POST' : POST}
        if method in allowed_methods:
            return allowed_methods[method]()
        else:
            return HTTPMethodNotAllowed()(environ, start_response)


    def __call__(self, environ, start_response):
        path_info = environ.get('PATH_INFO', '')
        method = environ.get('REQUEST_METHOD', '')
        func, query_dict = self.m.match(path_info)
        # TODO: Make this check more reliable
        #   This needs to check if it really is an APP URL request
        #   or whether the APP impl should pass it along to the next
        #   part of the wsgi chain.
        #   This pattern could probably be made generic.
        if func:
            return func(self, environ, start_response, method, **query_dict)
        else:
            return self.application(environ, start_response)

    urls = (
        (r'/(?P<collection>[a-zA-Z0-9_.:;-=]+)/(?P<entry>.*)', entry),
        (r'/(?P<collection>[a-zA-Z0-9_.:;-=]+)/?', collection),
        (r'/(?P<service>)?', service),
        (r'(?P<service>)?', service),
        (r'', service),
    )

# not supported yet
##     entry_urls = (
##         (r'(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[a-z\-\;\)\:]+)', entry),
##     )


class AtomProtocolPlugins(object):
    def __init__(self, application):
        self.application = application
        self.m = UrlDispatch(self.urls)

    def get_date_from_entry(self, entry):
        dateformat = '%Y-%m-%dT%H:%M:%SZ'
        date_string = str(entry.published)
        entry_date = datetime(*(time.strptime(date_string, dateformat)[0:6]))
        return entry_date

    def create_bc_link(self, entry, base):
        entry_date = self.get_date_from_entry(entry)
        title = get_clean_title(entry.title)
        href = '%s%s/%s' % (base, entry_date.strftime('%Y/%m/%d'), title)
        return '<link href="%s" rel="permalink" title="permalink" />' % href
    
    def create_entry(self, environ, start_response, method, id=None):
        result = self.application(environ, start_response)
        atom = amara.parse(''.join(result[0]), prefixes=COMMON_PREFIXES)
        for i in range(0, len(atom.feed.entry)):
            base = get_base_url(environ)
            entry = atom.feed.entry[i]
            if u'author' not in entry.xml_child_elements.keys():
                atom.feed.entry[i].xml_append_fragment(atom.feed.author.xml())
            link = self.create_bc_link(entry, base)
            atom.feed.entry[i].xml_append_fragment(link)
        buffer = cStringIO.StringIO()

    def __call__(self, environ, start_response):
        path_info = environ.get('PATH_INFO', '')
        method = environ.get('REQUEST_METHOD', '')
        func, query_dict = self.m.match(path_info)
        if func:
            return func(self, environ, start_response, method, **query_dict)
        else:
            return self.application(environ, start_response)

    urls = (
        (POST_COLLECTION_TRIGGER%('(?P<id>.*)'), create_entry),
    )


#Only supports flatfile repo for now.  Have to make that configurable
from brightcontent.core.store import flatfile_repository

from paste.deploy.config import ConfigMiddleware
from paste.recursive import RecursiveMiddleware
from paste import httpexceptions 

def make_app(global_conf, **app_conf):
    repository_cname = 'flatfile_repository'
    store = flatfile_repository(flatfile_storedir='entries')
    app = ConfigMiddleware(None, app_conf)
    app = WsgiAtomPublishingProtocol(app, store)
    app = AtomProtocolPlugins(app)
    app = RecursiveMiddleware(app, global_conf)
    app = httpexceptions.make_middleware(app)
    return app


